using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Xml;
using System.Threading;
using Microsoft.WindowsMobile.Status;
using System.Runtime.CompilerServices;

namespace FeedFly
{   
    /// <summary>
    /// Static helper class for network operations
    /// </summary>
    public static class Net
    {
        private static bool StopDownloading, QueueDirty;
        private static Mutex DownloadMutex = new Mutex();
        private static Queue<DownloadRequest> DownloadQueue;
        private static System.Threading.Timer DownloadTimer;
        //private static string DownloadLogFilePath;
        private static SystemState NetworkWatcher;
        private const string ROOT_FOLDER = "1";
        private const string INTERNET_TEST_URL = "http://www.google.com/m";
        private const string INTERNET_TEST_TEXT = "Google";
        private const string INTERNET_TEST_METHOD = "GET";
        private const int INTERNET_RETRY_LIMIT = 5;
        private const int INTERNET_SLEEP_INTERVAL_MS = 100; // milliseconds
        private static bool InternetReachable;

        public struct DownloadRequest
        {
            public Feed Feed;
            public DateTime RequestedOn;
            public bool Retried;

            public DownloadRequest(Feed f)
            {
                this.Feed = f;
                this.RequestedOn = DateTime.Now;
                this.Retried = false;
            }

            public override bool Equals(object obj)
            {   
                DownloadRequest dr = (DownloadRequest)obj;
                return Feed.Equals(dr.Feed);
            }
        }        
        
        public struct DownloadResponse
        {
            //public XmlReader FeedReader;
            public HttpWebResponse HTTPResponse;
            public string Address;
        }

        /// <summary>
        /// Initialization method for static Net class.
        /// </summary>
        public static void Init()
        {
            QueueDirty = true;

            //DownloadLogFilePath = App.GetApplicationPath() + "DownloadLog.txt";

            DownloadQueue = new Queue<DownloadRequest>(20);

            // add event handler for connection count changes
            NetworkWatcher = new SystemState(SystemProperty.ConnectionsCount, false);
            NetworkWatcher.Changed += new ChangeEventHandler(NetworkWatcher_Changed);

            // initialize flags for internet connection
            DetermineNetworkState(SystemState.ConnectionsCount);
                
            DownloadTimer = new System.Threading.Timer(
                new TimerCallback(DownloadTimer_Method)
                , null // object argument for method
                , new TimeSpan(0, 0, 0) // start delay
                , new TimeSpan(0, Settings.DownloadInterval, 0)); // repeat period
        }

        private static void NetworkWatcher_Changed(object sender, ChangeEventArgs args)
        {
            int connectionsCount = (int)args.NewValue;
            DetermineNetworkState(connectionsCount);
        }

        /// <summary>
        /// This method is a slightly modified version of the Internet connection
        ///  testing code found on http://msdn2.microsoft.com/en-us/netframework/bb851561.aspx
        /// The reason that the stream is saved to a string and compared against text
        ///  is because some networks might respond with a page that explains we
        ///  don't have access to this requested site without actually throwing an error.
        /// </summary>        
        private static bool IsInternetReachable()
        {
            bool isReachable = false;
            HttpWebRequest httpRequest = null;
            HttpWebResponse httpResponse = null;
            Stream responseStream = null;
            StreamReader responseReader = null;

            for (int connectTryCount = 0;
                !isReachable && connectTryCount < INTERNET_RETRY_LIMIT;
                connectTryCount++)
            {
                try
                {
                    httpRequest = (HttpWebRequest)WebRequest.Create(INTERNET_TEST_URL);
                    httpRequest.Method = INTERNET_TEST_METHOD;
                    httpResponse = (HttpWebResponse)httpRequest.GetResponse();
                    responseStream = httpResponse.GetResponseStream();
                    responseReader = new StreamReader(responseStream);
                    string responseText = responseReader.ReadToEnd();
                    isReachable = responseText.IndexOf(INTERNET_TEST_TEXT) >= 0;
                }
                catch
                {
                }
                finally
                {
                    if (responseReader != null)
                        responseReader.Close();
                    if (responseStream != null)
                        responseStream.Close();
                    if (httpResponse != null)
                        httpResponse.Close();
                }

                Thread.Sleep(INTERNET_SLEEP_INTERVAL_MS);
            }
            
            return isReachable;
        }

        private static void DetermineNetworkState(int connectionsCount)
        {
            if (connectionsCount > 0)
                InternetReachable = IsInternetReachable();
            else
                InternetReachable = false;
        }

        /// <summary>
        /// Downloads feed from web address into XmlReader
        /// </summary>
        public static DownloadResponse Download(string address)
        {
            HttpWebRequest httpRequest = null;
            HttpWebResponse httpResponse = null;
            
            DownloadResponse response = new DownloadResponse();
            response.Address = address;

            if (Net.InternetReachable)
            {
                try
                {
                    // create the web request for the url/string
                    httpRequest = (HttpWebRequest)WebRequest.Create(address);
                    httpResponse = (HttpWebResponse)httpRequest.GetResponse();

                    response.HTTPResponse = httpResponse;
                }
                catch
                {
                }
            }

            return response;
        }

        private static void DownloadTimer_Method(object o)
        {
            // prevent from continually adding feeds to download queue
            // if no items have been dequeued yet
            if (InternetReachable)
            {
                if (QueueDirty && !StopDownloading)
                {
                    Folder.DownloadUpdate(ROOT_FOLDER);
                    QueueDirty = false;
                }

                // attempt to download feeds from queue
                Net.ProcessDownloads(null);
            }
        }
        
        // used to add a feed to the queue
        public static void EnqueueDownload(Feed f)
        {
            DownloadRequest dr = new DownloadRequest(f);
            dr.RequestedOn = DateTime.Now;

            // if it doesn't already contain this feed
            //   add it to the queue
            if (!DownloadQueue.Contains(dr))
                DownloadQueue.Enqueue(dr);
        }

        // used in case I need to clear the queue
        //     haven't actually used it yet, but for the future
        public static void ClearQueue()
        {
            DownloadQueue.Clear();
        }

        // will stop the queue from downloading
        public static void StopDownloads()
        {
            StopDownloading = true;
        }

        // used to resume the downloads
        public static void ResumeDownloads()
        {
            StopDownloading = false;
        }

        // Used to process Downloads
        public static void ProcessDownloads(object stateInfo)
        {
            DownloadRequest dr;
            bool hasItems;

            // lock mutex to make queue processing thread-safe
            DownloadMutex.WaitOne();

            // append to download log file
            //StreamWriter sw = new StreamWriter(Net.DownloadLogFilePath, true);
            //sw.WriteLine(DateTime.Now);
            
            try
            {
                // modified to add checks for flags
                while (InternetReachable && !StopDownloading && DownloadQueue.Count > 0)
                {
                    hasItems = false;
                    
                    // moved dequeue to bottom if download fails because of network connection
                    dr = DownloadQueue.Peek();

                    // write operation to download queue
                    //sw.WriteLine(dr.Feed.Address);

                    // check format of feed first
                    //   if unknown, it's a new feed and format needs to be discovered
                    if (dr.Feed.Format == FeedFormat.Unknown)                            
                        dr.Feed = Feed.GetFeed(dr.Feed.FolderId, dr.Feed.Address, dr.Feed.ItemLimit);                         
                    else                            
                        dr.Feed.DownloadUpdate();

                    if (dr.Feed == null)
                    {
                        // If null is returned, feed address couldn't be downloaded
                        // This could be because the network connection was just lost
                        //  or that the address is bad. 
                        // Check the connection flag to make sure we still have a connection.
                        if (InternetReachable)
                        {
                            // get back original request
                            dr = DownloadQueue.Dequeue();

                            // if this feed has already been retried, most likely it's a bad address
                            if (dr.Retried)
                            {
                                // notify user that feed address was bad                  
                                StringBuilder msg = new StringBuilder("The feed at<br />", 200);
                                msg.Append(dr.Feed.Address).Append(" could not be reached after 2 attempts and was removed from the download queue.");
                                Screens.Main.QueueNotification(new NotificationMessage(msg.ToString()));
                            }
                            else
                            {
                                // move feed to end of queue and set retried flag to give this address one more try                                
                                dr.Retried = true;
                                DownloadQueue.Enqueue(dr);
                            }                            
                        }
                    }
                    else
                    {
                        // a cancelled download indicates a problem with the feed, so remove from queue
                        if (dr.Feed.DownloadCancelled)
                        {
                            // remove from download queue
                            DownloadQueue.Dequeue();

                            // TODO: set flag in db to stop downloading this feed
                        }
                        else
                        {
                            // check aborted flag to make sure problem didn't occur during parsing
                            if (!dr.Feed.DownloadAborted)
                            {
                                // get item count before Persist() is called and empties item collection
                                hasItems = dr.Feed.Items.Count > 0;

                                try
                                {
                                    //  moved persist to be inline since dequeue hasn't been called yet.
                                    dr.Feed.Persist();
                                }
                                catch (Exception e)
                                {
                                    StringBuilder dbError = new StringBuilder("Could not save feed into database from<br />", 400);
                                    dbError.Append(dr.Feed.Address).Append("<br /><br />").Append(e.Message);

                                    Screens.Main.QueueNotification(new NotificationMessage(NotificationMessageType.Error, dbError.ToString()));

                                    // make sure feed doesn't attempt downloading/persisting continuously
                                    dr.Feed.DownloadCancelled = true;
                                }

                                if (dr.Feed.Persisted)
                                {
                                    // Attempt to refresh listing forms if related to current feed.
                                    //     Only refresh feed listing if new feed was created to have it sort properly
                                    //      existing feed unread counts are updated in UI during Persist()
                                    if (dr.Feed.IsNew) Screens.Main.RefreshFolder(dr.Feed.FolderId);
                                    // don't bother refreshing item list screen if no items were downloaded
                                    if (hasItems) Screens.ItemList.RefreshFeed(dr.Feed.Id);

                                    // Moved dequeue to end of loop to keep feed in queue until it has been downloaded
                                    DownloadQueue.Dequeue();

                                    // After dequeue has been made, flag queue as dirty to enable automatic downloads
                                    QueueDirty = true;
                                }
                            }
                        }
                    }                                       
                }                
            }
            catch (Exception ex)
            {
                //throw;
                StringBuilder exMsg = new StringBuilder("When processing the download queue, the following exception occured.<br /><br />", 200);
                Screens.Main.QueueNotification(new NotificationMessage(NotificationMessageType.Error, exMsg.Append(ex.Message).ToString()));
            }
            finally
            {
                //sw.Close();                
                DownloadMutex.ReleaseMutex();
            }            
        }
        

    }
}
